/**
 * 
 */
package name.yzhu.wordsearch;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import name.yzhu.wordsearch.CharMatrix.CharMatrixWord;

/**
 * @author yhzhu
 *
 */
public class WordSearch {
	
	private WordList wordList = null;
	private Map<Character, TrieTree> trieTreeMap = new HashMap<Character, TrieTree>();
	/**
	 * initial word search
	 * @param wordListFile
	 * @throws IOException 
	 */
	public void open(String wordListFile) throws IOException{
		try {
			wordList = WordList.getWordListFactory(wordListFile);
			
			constructTrieTree(wordList.getCharMatrix());
		} catch (IOException e) {
			throw e;
		}
	}
	
	private void constructTrieTree(CharMatrix charMatrix) {
	    List<CharMatrixWord> words = new ArrayList<CharMatrix.CharMatrixWord>();
	    words.addAll(charMatrix.getWordsFromTopLeft());
	    words.addAll(charMatrix.getWordsFromBottomRight());
	    words.addAll(charMatrix.getWordsFromLeftMiddle());
	    words.addAll(charMatrix.getWordsFromTopMiddle());
	    words.add(charMatrix.getWordsFromLeftBottomToRightTop());
	    
	    for (CharMatrixWord cw: words) {
	    	TrieTree trieTree = trieTreeMap.get(cw.getWord().charAt(0));
	    	if (trieTree == null){
	    		trieTree = new TrieTree();
	    		trieTreeMap.put(cw.getWord().charAt(0), trieTree);
	    	}
	    	trieTree.addWord(cw.getWord(), cw.getPoints().toArray());
	    }
	}

	/**
	 * destroy resources
	 */
	public void close(){
	}
	/**
	 * 
	 * @param query
	 * @return
	 */
	public Coordinates search(String query){
	    if (!preCheckQuery(query))
	    	return null;
	    
		return search(query, wordList.getSearchMode());
	}

	private Coordinates search(String query, SearchMode searchMode) {
		Coordinates cd = searchWithNoWrap(query, false);
		if (cd != null)
			return cd;
		
		// keep to search trie tree after revert word
        cd = searchWithNoWrap(revertString(query), true);
		if (cd != null)
			return cd;
		
		if (searchMode == SearchMode.WRAP){
			return searchWithWrap(query);
		}
		return null;
	}
	
	private Coordinates searchWithWrap(String query) {
		Coordinates cd = new Coordinates();
		for (int i=0; i<query.length(); i++){
			char c = query.charAt(i);
			Position pos = wordList.getCharMatrix().getBoundaryCharPosition(c);
			if (pos == null)
				return null;
			if (i == 0)
				cd.setStart(new Position(pos));
			cd.setEnd(pos);
		}
		return cd;
	}

	private Coordinates searchWithNoWrap(String word, boolean revert) {
		TrieTree trieTree = trieTreeMap.get(word.charAt(0));
		if (trieTree == null)
			return null;
		Object[] data = trieTree.get(word);
		if (data != null){
			Coordinates cd = new Coordinates();
			if (revert){
			    cd.setEnd((Position) data[0]);
			    cd.setStart((Position) data[data.length - 1]);
			}
			else{
			    cd.setStart((Position) data[0]);
			    cd.setEnd((Position) data[data.length - 1]);
			}
			return cd;
		}
		return null;
	}
    /**
     * check the query should have any duplicated letter
     * @param query
     * @return
     */
	private boolean preCheckQuery(String query) {
		long flag = 0L;
		for (char c: query.toCharArray()){
			int idx = c - TrieTree.MIN_CHAR;
			long offset = 1 << idx;
			if ((flag & offset) > 0)
				return false;
			flag = flag | offset;
		}
		return true;
	}
	
	private String revertString(String word){
		StringBuilder sb = new StringBuilder();
		for (int i=word.length()-1; i>=0; i--){
			sb.append(word.charAt(i));
		}
		return sb.toString();
	}
	
	public Map<Character, TrieTree> getTierTreeMap() {
		return trieTreeMap;
	}
	
	public static class Coordinates {
		private Position start = null;
		private Position end = null;
		
		public Coordinates(){}
		
		public Coordinates(Position s, Position e) {
			 start = new Position(s);
			 end = new Position(e);
		}
		public Position getStart() {
			return start;
		}
		public void setStart(Position start) {
			this.start = start;
		}
		public Position getEnd() {
			return end;
		}
		public void setEnd(Position end) {
			this.end = end;
		}
		@Override
		public String toString() {
			return "("+start.getFirst()+", "+start.getSecond()+
					") ("+end.getFirst()+", "+end.getSecond()+")";
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((end == null) ? 0 : end.hashCode());
			result = prime * result + ((start == null) ? 0 : start.hashCode());
			return result;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Coordinates other = (Coordinates) obj;
			if (end == null) {
				if (other.end != null)
					return false;
			} else if (!end.equals(other.end))
				return false;
			if (start == null) {
				if (other.start != null)
					return false;
			} else if (!start.equals(other.start))
				return false;
			return true;
		}	
	}
}
